package com.stateofflow.invariantj;

import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.Arrays;

public class InvariantViolationError extends Error {
    private final NamedThrowableMap exceptions;
    private final String[] failures;

    public InvariantViolationError(String[] failures, NamedThrowableMap exceptions, Throwable thrownInCheckedMethod) {
        super(thrownInCheckedMethod);
        this.exceptions = exceptions;
        this.failures = failures;
    }
    
    public String getMessage() {
        StringBuffer sb = new StringBuffer();
        
        appendCauseMessage(sb);
        appendFailuresMessage(sb);
        appendExceptionsMessage(sb);
        
        return sb.toString();
    }

    private void appendExceptionsMessage(StringBuffer sb) {
        if (exceptions != null && !exceptions.isEmpty()) {
            addSpaceIfNotEmpty(sb);
            sb.append("Exceptions thrown while detecting invariant violations (details in stack trace): ").append(Arrays.asList(exceptions.getNames())).append('.');
        }
    }

    private void appendFailuresMessage(StringBuffer sb) {
        if (failures != null && failures.length > 0) {
            addSpaceIfNotEmpty(sb);
            sb.append("Invariant Violations: ").append(Arrays.asList(failures)).append('.');
        }
    }

    private void appendCauseMessage(StringBuffer sb) {
        if (getCause() != null) {
            addSpaceIfNotEmpty(sb);
            sb.append("Method terminated due to exception/error (see 'Caused by').");
        }
    }

    private void addSpaceIfNotEmpty(StringBuffer sb) {
        if (sb.length() > 0) {
            sb.append(' ');
        }
    }

    public NamedThrowableMap getTrappedExceptions() {
        return exceptions;
    }
    
    public void printStackTrace(PrintWriter writer) {
        super.printStackTrace(writer);
        
        String[] throwableNames = exceptions.getNames();
        for (int i = 0 ; i < throwableNames.length ; i++) {
            Throwable t = exceptions.get(throwableNames[i]);
            writer.println("While checking invariant " + throwableNames[i] + " the following was thrown:");
            t.printStackTrace(writer);
        }
    }
    
    public void printStackTrace(PrintStream stream) {
        PrintWriter writer = new PrintWriter(new OutputStreamWriter(stream));
        printStackTrace(writer);
        writer.close();
    }
}